/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package simplemetrosystem.control.Calculation;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Observable;
import java.util.Random;
import simplemetrosystem.control.ElementManager;
import simplemetrosystem.model.Element.CalElement;
import simplemetrosystem.model.Element.CalStation;
import simplemetrosystem.model.Element.MetroEdge;
import simplemetrosystem.model.Element.MetroLine;
import simplemetrosystem.model.Element.MetroStation;
import simplemetrosystem.model.charlanguage.NameMap;
import simplemetrosystem.view.MetroFrame;
import simplemetrosystem.view.canvas.CanvasModel;

/**
 *
 * @author Eric Qian
 */
public class CalculManager extends Observable {

    public static CalculManager getInstance() {
        return calculManager;
    }

    public static CalculManager newInstance(MetroFrame frame) {
        calculManager = new CalculManager(frame);
        return calculManager;
    }

    private CalculManager(MetroFrame frame) {
        nameMap = NameMap.getInstance();
        this.frame = frame;
        elementManager = ElementManager.getInstance();
        random = new Random();
        bestWay = new MetroEdge[0];
        findWays = new ArrayList<FindWay>();
        findWays.add(new FindByDistance());
        findWays.add(new FindByTime());
        currentWay = findWays.get(0);
        success = false;
    }

    public void cUpdate() {
        this.setChanged();
        this.notifyObservers();
    }

    public boolean isMap() {
        elementManager = ElementManager.getInstance();

        if (elementManager.getLinesSize() < 3 ||
                elementManager.getStationsSize() < 1 ||
                elementManager.getEdgesSize() < 1) {
            return false;
        }

        for (int i = 0; i < elementManager.getLinesSize(); i++) {
            if (elementManager.getLineAt(i).getStatus().equals(MetroLine.STATUS_METRO)) {
                MetroLine aLine = elementManager.getLineAt(i);
                //初始化访问表
                HashMap<MetroStation, Boolean> visited =
                        new HashMap<MetroStation, Boolean>();
                for (int j = 0; j < aLine.getStationsSize(); j++) {
                    visited.put(aLine.getStationAt(j), new Boolean(false));
                }

                if (aLine.getStationsSize() > 0) {
                    MetroStation aStation =
                            aLine.getStationAt(random.nextInt(aLine.getStationsSize()));
                    if (!findNext(aStation, aLine, visited)) {
                        return false;
                    }
                }
            }
        }
        buildMap();
        return true;
    }

    private boolean findNext(MetroStation aStation,
            MetroLine aLine, HashMap<MetroStation, Boolean> visited) {
        visited.put(aStation, new Boolean(true));
        for (int i = 0; i < aStation.getEdgesSize(); i++) {
            MetroEdge aEdge = aStation.getEdgeAt(i);
            if (aEdge.getLine() == aLine) {
                if (aEdge.getLine() == aLine) {
                    MetroStation nStation =
                            aEdge.getStationA() == aStation ? aEdge.getStationB() : aEdge.getStationA();
                    if (!visited.get(nStation).booleanValue()) {
                        findNext(nStation, aLine, visited);
                    }
                }
            }
        }
        Collection<Boolean> values = visited.values();
        for (Boolean aValue : values) {
            if (aValue.booleanValue() == false) {
                return false;
            }
        }
        return true;
    }

    public void buildMap() {
        elementManager = ElementManager.getInstance();
        buildConnectedMatrix();
        addSerialNumber();
        sortStationOfEdge();
    }

    public void addSerialNumber() {
        for (int i = 0; i < elementManager.getLinesSize(); i++) {
            if (elementManager.getLineAt(i).getStatus().equals(MetroLine.STATUS_METRO)) {
                MetroLine aLine = elementManager.getLineAt(i);
                ArrayList<MetroStation> pointStations = new ArrayList<MetroStation>();
                number = 1;
                HashMap<MetroEdge, Boolean> visited = new HashMap<MetroEdge, Boolean>();
                for (int k = 0; k < aLine.getEdgesSize(); k++) {
                    visited.put(aLine.getEdgeAt(k), new Boolean(false));
                }

                for (int j = 0; j < aLine.getStationsSize(); j++) {
                    if (aLine.getStationAt(j).getEdgeOfLineSize(aLine) < 2) {
                        pointStations.add(aLine.getStationAt(j));
                    }
                }

                if (pointStations.size() > 1) {
                    pointEdge(pointStations.get(0), aLine, visited);
                } else if (pointStations.size() == 0) {
                    MetroStation pStation = aLine.getStationAt(i);
                    for (int k = 1; k < aLine.getStationsSize(); k++) {
                        if (pStation.getLinesSize() < aLine.getStationAt(k).getLinesSize()) {
                            pStation = aLine.getStationAt(k);
                        }
                    }
                    pointEdge(pStation, aLine, visited);
                } else {
                    System.err.println("CalculManager: One Point");
                }
                aLine.eUpdate();
                elementManager.sortEdges();
            }
        }
    }

    private void pointEdge(MetroStation aStation, MetroLine aLine,
            HashMap<MetroEdge, Boolean> visited) {
        for (int i = 0; i < aStation.getEdgesSize(); i++) {
            MetroEdge aEdge = aStation.getEdgeAt(i);
            if (aEdge.getLine() == aLine) {
                MetroStation nStation =
                        aEdge.getStationA() == aStation ? aEdge.getStationB() : aEdge.getStationA();
                if (!visited.get(aEdge).booleanValue()) {
                    aEdge.setSerialNumber(number++);
                    visited.put(aEdge, new Boolean(true));
                    pointEdge(nStation, aLine, visited);
                }
            }
        }
    }

    public void sortStationOfEdge() {
        for (int i = 0; i < elementManager.getLinesSize(); i++) {
            MetroLine aLine = elementManager.getLineAt(i);
            if (aLine.getStatus().equals(MetroLine.STATUS_METRO)) {
                MetroEdge aEdge = aLine.getEdgeAt(0);
                MetroStation current;
                ArrayList<MetroStation> visited = new ArrayList<MetroStation>();

                if (aEdge.getStationA().getEdgeOfLineSize(aLine) != 1) {
                    aEdge.exchangeStation();
                    if (aEdge.getStationA().getEdgeOfLineSize(aLine) != 1) {
                        if (aEdge.getStationA() == aLine.getEdgeAt(2).getStationA() ||
                                aEdge.getStationA() == aLine.getEdgeAt(2).getStationB()) {
                            aEdge.exchangeStation();
                        }
                    }
                }

                current = aEdge.getStationB();
                visited.add(aEdge.getStationA());
                visited.add(current);

                for (int j = 1; j < aLine.getEdgesSize(); j++) {
                    aEdge = aLine.getEdgeAt(j);
                    if (aEdge.getStationA() != current) {
                        aEdge.exchangeStation();
                        if (aEdge.getStationA() != current) {
                            if (visited.contains(aEdge.getStationB())) {
                                aEdge.exchangeStation();
                            }
                        }
                    }
                    current = aEdge.getStationB();
                }
            }
        }
    }

    public boolean isConnected(MetroStation stA, MetroStation stB) {
        elementManager = ElementManager.getInstance();

        if (connectMatrix == null) {
            buildMap();
        }

        for (int i = 0; i < stA.getLinesSize(); i++) {
            for (int j = 0; j < stB.getLinesSize(); j++) {
                if (stA.getLineAt(i).getStatus().equals(MetroLine.STATUS_METRO) &&
                        stB.getLineAt(j).getStatus().equals(MetroLine.STATUS_METRO)) {
                    int x = elementManager.getIndexOfLine(stA.getLineAt(i));
                    int y = elementManager.getIndexOfLine(stB.getLineAt(j));
                    if (connectMatrix[x][y] == true) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    public boolean isConnected(MetroLine lineA, MetroLine lineB) {
        if (lineA.getStatus().equals(MetroLine.STATUS_METRO) &&
                lineB.getStatus().equals(MetroLine.STATUS_METRO)) {
            int x = elementManager.getIndexOfLine(lineA);
            int y = elementManager.getIndexOfLine(lineB);
            if (connectMatrix[x][y] == true) {
                return true;
            }
        }
        return false;
    }

    public void buildConnectedMatrix() {
        elementManager = ElementManager.getInstance();
        int size = elementManager.getLinesSize() - 2;
        boolean[][][] matrixs = new boolean[size][size][size];
        matrixs = initMatrixs(matrixs);

        for (int i = 1; i < size; i++) {
            matrixs[i] = MatrixMultiply(matrixs[0], matrixs[i - 1]);
        }

        connectMatrix = new boolean[size][size];
        for (int i = 0; i < size; i++) {
            connectMatrix[i][i] = true;
        }
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                for (int k = 0; k < size; k++) {
                    connectMatrix[i][j] = connectMatrix[i][j] || matrixs[k][i][j];
                }
            //System.out.print(connectMatrix[i][j] + " ");
            }
            System.out.println("/n");
        }

    }

    public boolean[][] MatrixMultiply(boolean[][] ma, boolean[][] mb) {
        int size = ma.length;
        boolean[][] aMatrix = new boolean[size][size];
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                for (int k = 0; k < size; k++) {
                    aMatrix[i][j] = aMatrix[i][j] || (ma[i][k] && mb[k][j]);
                }
            }
        }
        return aMatrix;
    }

    /**
     * 
     * @param matrixs
     * @return
     */
    private boolean[][][] initMatrixs(boolean[][][] matrixs) {

        for (int i = 0; i < elementManager.getLinesSize(); i++) {
            MetroLine aLine = elementManager.getLineAt(i);
            if (aLine.getStatus().equals(MetroLine.STATUS_METRO)) {
                for (int j = 0; j < aLine.getStationsSize(); j++) {
                    if (aLine.getStationAt(j).getLinesSize() > 1) {
                        MetroStation aStation = aLine.getStationAt(j);
                        for (int k = 0; k < aStation.getLinesSize(); k++) {
                            if (aStation.getLineAt(k).getStatus().equals(MetroLine.STATUS_METRO)) {
                                int l = elementManager.getIndexOfLine(aStation.getLineAt(k));
                                matrixs[0][i][l] = true;
                                matrixs[0][l][i] = true;
                            }
                        }
                    }
                }
            } else {
                for (int j = 0; j < aLine.getEdgesSize(); j++) {
                    MetroStation sA = aLine.getEdgeAt(j).getStationA();
                    MetroStation sB = aLine.getEdgeAt(j).getStationB();
                    for (int a = 0; a < sA.getLinesSize(); a++) {
                        if (sA.getLineAt(a).getStatus().equals(MetroLine.STATUS_METRO)) {
                            for (int b = 0; b < sB.getLinesSize(); b++) {
                                if (sB.getLineAt(b).getStatus().equals(MetroLine.STATUS_METRO)) {
                                    int x = elementManager.getIndexOfLine(sA.getLineAt(a));
                                    int y = elementManager.getIndexOfLine(sB.getLineAt(b));
                                    matrixs[0][x][y] = true;
                                    matrixs[0][y][x] = true;
                                }
                            }
                        }
                    }
                }
            }
        }
        return matrixs;
    }

    public int getFindSize() {
        return findWays.size();
    }

    public String getFindName(int i) {
        return findWays.get(i).getName();
    }

    /**
     * 
     * @param i
     */
    public void setCurrentWay(int i) {
        currentWay = findWays.get(i);
    }

    public void findWay(boolean change) {
        if (stA == null || stB == null) {
            bestWay = new MetroEdge[0];
            cost = 0;
            success = false;
        } else if (stA != stB && isConnected(stA, stB)) {
            bestWay = currentWay.findWay(stA, stB);
            //for (int i = 0; i < bestWay.length; i++) {
            //    System.out.println(i + " " + bestWay[i].getDisplayName());
            //}
            cost = CostCalculation.getInstance().getCost(bestWay, change);
            success = true;
        } else {
            bestWay = new MetroEdge[0];
            cost = 0;
            success = false;
        }
    }

    public void setStationA(MetroStation stA) {
        this.stA = stA;
    //frame.getInfPane().setHighLight(new CalStation(this.stA, stB), true);
    }

    public void setStationB(MetroStation stB) {
        this.stB = stB;
    //frame.getInfPane().setHighLight(new CalStation(stA, this.stB), true);
    }

    public String getOutput() {
        String output = "";
        if (success) {
            DecimalFormat df = new DecimalFormat("0.000");
            output += nameMap.getName("Station A") + ": " + stA.getDisplayName() + "\n" +
                    nameMap.getName("Station B") + ": " + stB.getDisplayName() + "\n" +
                    nameMap.getName("Distance(km)") + ": " + df.format(getDistance()) + "\n" +
                    nameMap.getName("Cost") + ": " + cost + " " + nameMap.getName("Yuan") + "\n" +
                    nameMap.getName("Path") + ": " + "\n" + getWayName();
        } else {
            output += nameMap.getName("Error");
        }
        return output;
    }

    private String getWayName() {

        MetroStation currentStation = stA;
        String way = stA.getDisplayName() + " - ";
        for (int i = 0; i < bestWay.length; i++) {
            if (bestWay[i].getStationA() == currentStation) {
                currentStation = bestWay[i].getStationB();
            } else {
                currentStation = bestWay[i].getStationA();
            }
            way += currentStation.getDisplayName();

            if (i != bestWay.length - 1) {
                if (bestWay[i].getLine() != bestWay[i + 1].getLine()) {
                    way += "(" + nameMap.getName("Change Line") + ")";
                }
            }
            if (currentStation != stB) {
                way += " - ";
            }
            if (i % 2 == 0) {
                way += "\n";
            }
        }
        return way;
    }

    private double getDistance() {
        double distance = 0;
        for (int i = 0; i < bestWay.length; i++) {
            distance += bestWay[i].getDistance();
        }
        return distance;
    }

    public CalElement getCalElement() {
        return new CalElement(bestWay);
    }
    private static CalculManager calculManager;
    private MetroFrame frame;
    private ElementManager elementManager;
    private NameMap nameMap;
    private Random random;
    private boolean[][] connectMatrix;
    private FindWay currentWay;
    private MetroStation stA;
    private MetroStation stB;
    private ArrayList<FindWay> findWays;
    private MetroEdge[] bestWay;
    private int cost;
    private int number;
    private boolean success;
}